
//=============================================================================
/**
 *  @file    be_helper.cpp
 *
 * Provides helper classes to print generated code to the output
 *
 *  @author Aniruddha Gokhale Improvements by Carlos O'Ryan
 */
//=============================================================================

#include "be_helper.h"
#include "be_codegen.h"
#include "be_extern.h"
#include "ast_expression.h"
#include "idl_defines.h"
#include "utl_identifier.h"
#include "utl_idlist.h"
#include "utl_string.h"
#include "ace/OS_NS_string.h"
#include "ace/OS_NS_ctype.h"
#include "../../tao/Version.h"

static const char copyright[] =
"// -*- C++ -*-\n"
"/**\n"
" * Code generated by the The ACE ORB (TAO) IDL Compiler v" TAO_VERSION TAO_PATCH "\n"
" * TAO and the TAO IDL Compiler have been developed by:\n"
" *       Center for Distributed Object Computing\n"
" *       Washington University\n"
" *       St. Louis, MO\n"
" *       USA\n"
" * and\n"
" *       Distributed Object Computing Laboratory\n"
" *       University of California at Irvine\n"
" *       Irvine, CA\n"
" *       USA\n"
" * and\n"
" *       Institute for Software Integrated Systems\n"
" *       Vanderbilt University\n"
" *       Nashville, TN\n"
" *       USA\n"
" *       https://www.isis.vanderbilt.edu/\n"
" *\n"
" * Information about TAO is available at:\n"
" *     https://www.dre.vanderbilt.edu/~schmidt/TAO.html\n"
" **/";

TAO_NL::TAO_NL ()
{
  ACE_UNUSED_ARG (copyright);
}

TAO_NL_2::TAO_NL_2 ()
{
  ACE_UNUSED_ARG (copyright);
}

TAO_INDENT::TAO_INDENT (int do_now)
  :  do_now_ (do_now)
{
}

TAO_UNINDENT::TAO_UNINDENT (int do_now)
  :  do_now_ (do_now)
{
}

const TAO_NL be_nl;
const TAO_NL_2 be_nl_2;
const TAO_INDENT be_idt;
const TAO_INDENT be_idt_nl (1);
const TAO_UNINDENT be_uidt;
const TAO_UNINDENT be_uidt_nl (1);

// Methods of the TAO_OutStream class.

TAO_OutStream::TAO_OutStream ()
  : fp_ (nullptr),
    st_ (TAO_CLI_HDR),
    indent_level_ (0)
{
  for (unsigned long i = 0; i < be_global->tab_size (); ++i)
    {
      this->tab_unit_str_ += ' ';
    }
}

TAO_OutStream::~TAO_OutStream ()
{
  // Close the underlying I/O handle only if it exists.
  if (this->fp_ != nullptr)
    {
      ACE_OS::fclose (this->fp_);
      this->fp_ = nullptr;
    }

  indent_level_ = 0;
}

int
TAO_OutStream::open (const char *fname,
                     TAO_OutStream::STREAM_TYPE st)
{
  if (fname != nullptr)
    {
      // File name exists, open an I/O file handle.
      this->fp_ = ACE_OS::fopen (fname, "w");

      if (this->fp_ != nullptr)
        {
          this->st_ = st;
          // Put the copyright notice.  Not for the gperf's temp input
          // file.
          if (st != TAO_OutStream::TAO_GPERF_INPUT)
            {
              ACE_OS::fprintf (this->fp_,
                               "%s\n",
                               copyright);
            }

          return 0;
        }
      else
        {
          return -1;
        }
    }
  else
    {
      return -1;
    }
}

// Set and get the stream type.
void
TAO_OutStream::stream_type (TAO_OutStream::STREAM_TYPE st)
{
  this->st_ = st;
}

TAO_OutStream::STREAM_TYPE
TAO_OutStream::stream_type ()
{
  return this->st_;
}

// Return the underlying lowlevel file pointer.
// indentation.
FILE *&
TAO_OutStream::file ()
{
  return this->fp_;
}

int
TAO_OutStream::incr_indent (unsigned short flag)
{
  ++indent_level_;

  if (flag != 0)
    {
      return this->indent ();
    }
  else
    {
      // Do not indent output.
      return 0;
    }
}

// Indentation
int
TAO_OutStream::decr_indent (unsigned short flag)
{
  --this->indent_level_;
  // Just in case somebody gets "unindent happy".
  if (this->indent_level_ < 0)
    {
      // ACE_DEBUG ((LM_DEBUG, "negative indentation?\n"));
      this->indent_level_ = 0;
    }
  if (flag != 0)
    {
      return this->indent ();
    }
  else
    {
      // Do not indent output.
      return 0;
    }
}

int
TAO_OutStream::reset ()
{
  this->indent_level_ = 0;
  return 0;
}

// Indented print.
int
TAO_OutStream::indent ()
{
  // Based on the current indentation level, leave appropriate number of blank
  // spaces in the output.
  if (this->indent_level_ > 0)
    {
      for (int i = 0; i < this->indent_level_; i++)
        {
          ACE_OS::fprintf (this->fp_, "%s", this->tab_unit_str_.c_str ());
        }
    }

  return 0;
}

int
TAO_OutStream::nl ()
{
  ACE_OS::fprintf (this->fp_, "\n");
  this->indent ();
  return 0;
}

// Macro generation.
int
TAO_OutStream::gen_ifdef_macro (const char *flat_name,
                                const char *suffix,
                                bool add_stream_type_suffix)
{
  static char macro [NAMEBUFSIZE];

  ACE_OS::memset (macro,
                  '\0',
                  NAMEBUFSIZE);

  ACE_OS::sprintf (macro,
                   "_%s_",
                   tao_cg->upcase (flat_name));

  if (suffix != nullptr)
    {
      ACE_OS::strcat (macro, "_");
      ACE_OS::strcat (macro, tao_cg->upcase (suffix));
      ACE_OS::strcat (macro, "_");
    }

  // Append a suffix representing the stream type.
  if (add_stream_type_suffix)
    {
      switch (this->st_)
        {
          case TAO_OutStream::TAO_CLI_HDR:
            ACE_OS::strcat (macro, "CH_");
            break;
          case TAO_OutStream::TAO_CLI_INL:
            ACE_OS::strcat (macro, "CI_");
            break;
          case TAO_OutStream::TAO_CLI_IMPL:
            ACE_OS::strcat (macro, "CS_");
            break;
          case TAO_OutStream::TAO_SVR_HDR:
            ACE_OS::strcat (macro, "SH_");
            break;
          case TAO_OutStream::TAO_IMPL_HDR:
            ACE_OS::strcat (macro, "IH_");
            break;
          case TAO_OutStream::TAO_IMPL_SKEL:
            ACE_OS::strcat (macro, "IS_");
            break;
          case TAO_OutStream::TAO_SVR_INL:
            ACE_OS::strcat (macro, "SI_");
            break;
          case TAO_OutStream::TAO_SVR_IMPL:
            ACE_OS::strcat (macro, "SS_");
            break;
          default:
            return -1;
        }
    }

  *this << "\n\n#if !defined (" << macro << ")\n";
  *this << "#define " << macro;

  return 0;
}

int
TAO_OutStream::gen_endif ()
{
  *this << "\n\n#endif /* end #if !defined */";

  return 0;
}

// Printf style variable argument print.
int
TAO_OutStream::print (const char *format, ...)
{
  int result = 0;
  va_list ap;
  va_start (ap, format);
  ACE_OSCALL (::vfprintf (this->fp_,
                          format,
                          ap),
              int,
              result);
  va_end (ap);

  return result;
}

TAO_OutStream &
TAO_OutStream::operator<< (const char *str)
{
  ACE_OS::fprintf (this->fp_, "%s", str);
  return *this;
}

TAO_OutStream &
TAO_OutStream::operator<< (char ch)
{
  ACE_OS::fprintf (this->fp_, "%c", ch);
  return *this;
}

TAO_OutStream &
TAO_OutStream::operator<< (const ACE_CString &str)
{
  ACE_OS::fprintf (this->fp_, "%s", str.c_str ());
  return *this;
}

TAO_OutStream &
TAO_OutStream::operator<< (const ACE_CDR::UShort num)
{
  ACE_OS::fprintf (this->fp_,
                   "%hu",
                   num);

  return *this;
}

TAO_OutStream &
TAO_OutStream::operator<< (const ACE_CDR::Short num)
{
  ACE_OS::fprintf (this->fp_,
                   "%hd",
                   num);

  return *this;
}

TAO_OutStream &
TAO_OutStream::operator<< (const ACE_CDR::ULong num)
{
  ACE_OS::fprintf (this->fp_,
                   "%lu",
                   (unsigned long) num);

  return *this;
}

TAO_OutStream &
TAO_OutStream::operator<< (const ACE_CDR::Long num)
{
  ACE_OS::fprintf (this->fp_,
                   "%ld",
                   (long) num);

  return *this;
}

TAO_OutStream &
TAO_OutStream::operator<< (const ACE_CDR::ULongLong num)
{
  ACE_OS::fprintf (this->fp_,
                   ACE_UINT64_FORMAT_SPECIFIER_ASCII,
                   num);

  return *this;
}

TAO_OutStream &
TAO_OutStream::operator<< (const ACE_CDR::LongLong num)
{
  ACE_OS::fprintf (this->fp_,
                   ACE_INT64_FORMAT_SPECIFIER_ASCII,
                   num);

  return *this;
}

TAO_OutStream &
TAO_OutStream::operator<< (const TAO_NL&)
{
  ACE_OS::fprintf (this->fp_ ,
                   "\n");
  this->indent ();

  return *this;
}

TAO_OutStream &
TAO_OutStream::operator<< (const TAO_NL_2&)
{
  ACE_OS::fprintf (this->fp_ ,
                   "\n");
  ACE_OS::fprintf (this->fp_ ,
                   "\n");
  this->indent ();
  return *this;
}

TAO_OutStream &
TAO_OutStream::operator<< (const TAO_INDENT& i)
{
  this->incr_indent (0);

  if (i.do_now_)
    {
      this->nl ();
    }

  return *this;
}

TAO_OutStream &
TAO_OutStream::operator<< (const TAO_UNINDENT& i)
{
  this->decr_indent (0);

  if (i.do_now_)
    {
      this->nl ();
    }

  return *this;
}

TAO_OutStream &
TAO_OutStream::operator<< (Identifier *id)
{
  return this->print (id);
}

TAO_OutStream &
TAO_OutStream::operator<< (UTL_IdList *id)
{
  return this->print (id);
}

TAO_OutStream &
TAO_OutStream::operator<< (AST_Expression *expr)
{
  return this->print (expr);
}

TAO_OutStream &
TAO_OutStream::print (Identifier *id)
{
  ACE_OS::fprintf (this->fp_, "%s", id->get_string ());

  return *this;
}

TAO_OutStream &
TAO_OutStream::print (UTL_IdList *idl)
{
  bool first = true;
  bool second = false;
  Identifier *id = nullptr;

  for (UTL_IdListActiveIterator i (idl); !i.is_done (); i.next ())
    {
      if (!first)
        {
          *this << "::";
        }
      else if (second)
        {
          first = second = false;
        }

      // Print the identifier.
      id = i.item ();
      *this << id;

      if (first)
        {
          if (ACE_OS::strcmp (id->get_string (), "") != 0
              && ACE_OS::strcmp (id->get_string (), "::") != 0)
            {
              // Does not start with a "".
              first = false;
            }
          else
            {
              second = true;
            }
        }
    }

  return *this;
}

template <typename IntType>
void
signed_int_helper (TAO_OutStream &os, IntType value, IntType min, const char *specifier)
{
  /*
   * It seems that in C/C++ the minus sign and the bare number are parsed
   * separately for negative integer literals. This can cause compilers
   * to complain when using the minimum value of a signed integer because
   * the number without the minus sign is 1 past the max signed value.
   *
   * https://stackoverflow.com/questions/65007935
   *
   * Apparently the workaround is to write it as `VALUE_PLUS_ONE - 1`.
   */
  const bool min_value = value == min;
  if (min_value) ++value;
  os.print (specifier, value);
  if (min_value) os.print (" - 1");
}

TAO_OutStream&
TAO_OutStream::print (AST_Expression *expr)
{
  AST_Expression::AST_ExprValue *ev = expr->ev ();

  /// Never happens as far as I know, but just in case...
  if (ev == nullptr)
    {
      ACE_ERROR ((LM_ERROR,
                  ACE_TEXT ("TAO_OutStream::print() - ")
                  ACE_TEXT ("expression not evaluated\n")));

      return *this;
    }

  switch (ev->et)
    {
    case AST_Expression::EV_short:
      this->TAO_OutStream::print (ACE_INT32_FORMAT_SPECIFIER_ASCII, ev->u.sval);
      break;
    case AST_Expression::EV_ushort:
      this->TAO_OutStream::print (ACE_INT32_FORMAT_SPECIFIER_ASCII "%c", ev->u.usval, 'U');
      break;
    case AST_Expression::EV_long:
      signed_int_helper<ACE_CDR::Long> (
        *this, ev->u.lval, ACE_INT32_MIN, ACE_INT32_FORMAT_SPECIFIER_ASCII);
      break;
    case AST_Expression::EV_ulong:
      this->TAO_OutStream::print (ACE_UINT32_FORMAT_SPECIFIER_ASCII "%c", ev->u.ulval, 'U');
      break;
    case AST_Expression::EV_longlong:
      this->TAO_OutStream::print ("ACE_INT64_LITERAL (");
      signed_int_helper<ACE_CDR::LongLong> (
        *this, ev->u.llval, ACE_INT64_MIN, ACE_INT64_FORMAT_SPECIFIER_ASCII);
      this->TAO_OutStream::print (")");
      break;
    case AST_Expression::EV_ulonglong:
      this->TAO_OutStream::print ("ACE_UINT64_LITERAL (");
      this->TAO_OutStream::print (ACE_UINT64_FORMAT_SPECIFIER_ASCII,
                                  ev->u.ullval);
      this->TAO_OutStream::print (")");
      break;
    case AST_Expression::EV_float:
      this->TAO_OutStream::print ("%f%c", ev->u.fval, 'F');
      break;
    case AST_Expression::EV_double:
      this->TAO_OutStream::print ("%24.16G", ev->u.dval);
      break;
    case AST_Expression::EV_longdouble:
      break;
    case AST_Expression::EV_char:
      // isprint() sees \ and ' as printable characters
      // so we have to test for them first.
      if (ev->u.cval == '\\')
        this->TAO_OutStream::print ("'\\\\'");
      else if (ev->u.cval == '\'')
        this->TAO_OutStream::print ("'\\''");

      // This handles hex and octal escape sequences
      // that would print out as weird characters.
      else if (ACE_OS::ace_isprint (ev->u.cval))
        this->TAO_OutStream::print ("'%c'", ev->u.cval);
      else if (ACE_OS::ace_iscntrl (ev->u.cval))
        switch (ev->u.cval)
          {
            case '\n':
              this->TAO_OutStream::print ("'\\n'");
              break;
            case '\t':
              this->TAO_OutStream::print ("'\\t'");
              break;
            case '\r':
              this->TAO_OutStream::print ("'\\r'");
              break;
            case '\v':
              this->TAO_OutStream::print ("'\\v'");
              break;
            case '\f':
              this->TAO_OutStream::print ("'\\f'");
              break;
            case '\b':
              this->TAO_OutStream::print ("'\\b'");
              break;
            case '\a':
              this->TAO_OutStream::print ("'\\a'");
              break;
            case '\?':
              this->TAO_OutStream::print ("'?'");
              break;
          default:
            this->TAO_OutStream::print ("'\\x%x'", ev->u.oval);
          }
       else
        this->TAO_OutStream::print ("'\\x%x'", ev->u.oval);
      break;
    case AST_Expression::EV_wchar:
      this->TAO_OutStream::print ("L'%lc'", ev->u.wcval);
      break;
    case AST_Expression::EV_octet:
      this->TAO_OutStream::print ("0x%02x", ev->u.oval);
      break;
    case AST_Expression::EV_bool:
      this->TAO_OutStream::print ("%s", ev->u.bval ? "true" : "false");
      break;
    case AST_Expression::EV_string:
      this->TAO_OutStream::print ("\"%s\"", ev->u.strval->get_string ());
      break;
    case AST_Expression::EV_wstring:
      this->TAO_OutStream::print ("L\"%s\"", ev->u.wstrval);
      break;
    case AST_Expression::EV_enum:
      this->print (expr->n ());
      break;
    case AST_Expression::EV_int8:
      this->TAO_OutStream::print ("%d", ev->u.int8val);
      break;
    case AST_Expression::EV_uint8:
      this->TAO_OutStream::print ("%uu", ev->u.uint8val);
      break;
    default:
      break;
    }

  return *this;
}

void TAO_OutStream::insert_comment (const char *file, int line)
{
  *this << be_nl << "// TAO_IDL - Generated from" << be_nl
        << "// " << file << ':' << static_cast<ACE_CDR::ULong> (line)
        << be_nl_2;
}
